#!/usr/bin/perl -w
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
# Copyright 2016 Igor Kozhukhov <igor@dilos.org>
#

# auditxml takes the audit record description (.xml file) and
# generates the files needed for the C audit api. 

my $prog = $0; $prog =~ s|.*/||g;
my $usage = <<EOF;

Usage: $prog [options] <xml-input-file>
Options:
	-d	Enable debug output
	-e pfx	Internal event prefix (default: AUE)
	-i pfx	Interface prefix (default: adt)
		External event prefix is uppercase version of this string.
	-o dir	Output directory (default: current dir)

EOF

use FindBin;
use lib "$FindBin::Bin/";

use auditxml;
use Getopt::Std;
use strict;

our $debug = 0; # normal use is to set via the file being parsed.
               # <debug set="on"/> or <debug set="off"/> or <debug/>
               # if the set attribute is omitted, debug state is toggled
               # Override with appDebug, but toggle won't do what you
               # want.
my $appDebug = 0; # used after return from "new auditxml";

# Process command-line options
our ($opt_d, $opt_e, $opt_i, $opt_o);
$opt_e = "";
$opt_i = "";
$opt_o = "";
if (!getopts('de:i:o:') || $#ARGV != 0) {
    die $usage;
}
my $outdir = $opt_o || ".";
my $pfx_adt = lc($opt_i) || "adt";
my $pfx_ADT = uc($pfx_adt);
my $pfx_AUE = uc($opt_e) || "AUE";

$appDebug = $opt_d;

my $uniLabel = "adr";
my $xlateUniLabelInc = 0;


# where everything comes from and where it goes:

my $xlateFile = "$outdir/${pfx_adt}_xlate.c";
my $headerFile = "$outdir/${pfx_adt}_event_N.h";

my $filename = $ARGV[0];  # input XML file
my $doc = new auditxml ($filename);
$filename =~ s|.*/||g;

$debug = $appDebug;

my $genNotice = "
DO NOT EDIT. This file is auto generated by the Solaris Audit
system from $filename.

See http://opensolaris.org/os/project/audit/
";

# trim leading/trailing newlines
$genNotice =~ s/^\n//s;
$genNotice =~ s/\n$//s;

my %xlateEventTable = ();
my @xlateTypeList = ();
my %xlateTypeList = ();
my %eventAPI = ();
my %eventExtra = ();
my %headers = ();
my %externalIdNo = ();
my @outputState = ();
my %nameTranslation = ();
my @xlateDefaults = ();
my %xlateDefault = ();
my %msg_list = ();

my $event;
while ($event = $doc->getNextEvent()) {
    my $eventId = $event->getId();
    my $eventHeader = $event->getHeader();
    my $idNo = $event->getIdNo();
    $externalIdNo{$eventId} = $idNo;
    addHeader($eventHeader) if defined ($eventHeader);
    my $super;
    my $omit = $event->getOmit();
    my $eventType = '';
    if ($super = $event->getSuperClass()) {
	$event = $super;
	$eventType = 'instance';
    } else {
	$eventType = $event->getType();
    }

    # header file for API use
    generateAPIFile($event, $eventId, $eventType, $eventHeader, $idNo)
        unless $omit eq 'always';

    # c file table for translation
    generateTableC($event, $eventId, $eventType, $eventHeader, $omit);
}

my $textList;
while ($textList = $doc->getNextMsgId()) {
    generateMsgLists($textList);  # enum -> text mappings
}

printTableC($xlateFile);
printAPIFile($headerFile, $doc);

exit 0;


sub printTableC {
    my $file = shift;

    unless (open(Cfile, ">$file")) {
	print STDERR "can't open output file ($file): $!\n";
	return;
    }

    my $notice = $genNotice;
    $notice =~ s/\n/\n * /gs;
    $notice =~ s/\s+\n/\n/gs;
    print Cfile <<EOF;
/*
 * $notice
 */

#include <bsm/libbsm.h>
#include <adt_xlate.h>
#include <libintl.h>

EOF
    print Cfile "#ifndef _PRAUDIT\n";
    print Cfile "/* Internal data type definitions */\n\n";
    my $extDef;
    foreach $extDef (@xlateTypeList) {
      print Cfile "static $extDef\n";
    }
    @xlateTypeList = ();

    print Cfile "\n/* External event structure to internal event structure */\n\n";

    my @pointers = ();

    foreach my $eventId (sort keys %xlateEventTable) {
	if ($xlateEventTable{$eventId}) {
	    my ($ref1, $eventType, $firstToken, $eventHeader) =
	      @{$xlateEventTable{$eventId}};
	    my @entries = @$ref1;
	    my $entry;
	    my $entries = $#entries;
	    my $count = $entries + 1;
	    my $externalName = $nameTranslation{$eventId};
	    my $externalRoot = $externalName;
	    $externalRoot =~ s/${pfx_AUE}_//;
	    my $structName = "XX_$externalRoot";
	    my $root = $eventId;
	    $root =~ s/${pfx_AUE}_//;
	    my $externalId = $eventId;
	    $externalId =~ s/${pfx_AUE}_/${pfx_ADT}_/;

	    unless ($eventType eq 'generic') {
		print Cfile "static struct entry $structName\[$count\] = {\n";
		foreach $entry (@entries) {
		    if ($entries--) {
			$entry =~ s/EOL/,/;
		    }
		    else {
			$entry =~ s/EOL//;
		    }
		    $entry =~ s/selfReference/$structName/;
		    print Cfile "\t$entry\n";
		}
		print Cfile "};\n";

		print Cfile "static struct translation X_$externalRoot = {\n";
		push (@pointers, "X_$externalRoot");

		print Cfile "\t0,\n";   # tx_offsetsCalculated = 0
		print Cfile "\t$externalId,\n";
		print Cfile "\t$externalName,\n";

		print Cfile "\t$count,\n";
		print Cfile "\t&XX_$externalRoot\[$firstToken\],\n";
		print Cfile "\t&XX_$externalRoot\[0\]\n};\n";
	    }
	} else {
	    print STDERR "expected entry for $eventId but none found\n";
	}
    }

    my $count = $#pointers + 2;
    print Cfile "adt_translation_t *${pfx_adt}_xlate_table[$count] = {\n";

    my $firstEvent = 1;
    foreach my $eventId (@pointers) {
	if ($firstEvent) {
	    $firstEvent = 0;
	}
	else {
	    print Cfile ",\n";
	}
	print Cfile "\t&$eventId";
    }
    print Cfile ",\n\tNULL\n};\n";

    # generate the Event preload() function

    print Cfile <<EOF;

void
${pfx_adt}_preload(au_event_t event_id, adt_event_data_t *event_data)
{
	switch (event_id) {
EOF

        foreach my $id (@xlateDefaults) {
		my $adtID = $id;
		$adtID =~ s/${pfx_AUE}/${pfx_ADT}/;

		print Cfile <<EOF;
	case $adtID:
EOF
		my @preloads = @{$xlateDefault{$id}};
		while (@preloads) {
			my $fieldName = shift @preloads;
			my $default = shift @preloads;
			$id =~ s/${pfx_AUE}_/${pfx_adt}_/;

			print Cfile <<EOF;
		event_data->$id.$fieldName = $default;
EOF
		}

		print Cfile <<EOF;
		break;
EOF
	}

    print Cfile <<EOF;
	default:
		break;
	}
}
#endif

EOF

    print Cfile "/* message lists */\n\n";
    my $listName;
    my @listName;
    foreach $listName (sort keys %msg_list) {
        my ($listRef, $headref) = @{$msg_list{$listName}};
	my ($header, $start, $public, $deprecated) = @$headref;

	my @listValue =  @$listRef;
	my $listValue;
	my $listLength = $#listValue + 1;

	$listName = 'NULL' if ($#listValue < 0);

        push (@listName, [$listName, $listLength - 1, $start, $public]);

	next if ($#listValue < 0);

	print Cfile "/* Deprecated message list */\n" if ($deprecated);
	print Cfile "static char *msg_$listName\[$listLength] = {\n";

	my $ffirst = 1;
	foreach $listValue (@listValue) {
	    print Cfile ",\n" unless $ffirst;
	    $ffirst = 0;
	    my ($id, $text) = split(/\s*::\s*/, $listValue);
	    if ($text) {
	        print Cfile "\t\"$text\"";
	    }
	    else {
	        print Cfile "\tNULL";
	    }
	}
	print Cfile "\n};\n";
    }

    if ($#listName >= 0) {
	print Cfile "\nstruct msg_text ${pfx_adt}_msg_text[", $#listName + 1,
			"] = {\n";
	my $ffirst = 1;
	foreach $listName (@listName) {
            my ($name, $max, $start) = @$listName;
	    $start = -$start if $start;
            print Cfile ",\n" unless $ffirst;
	    $ffirst = 0;
	    $name = "msg_$name" if ($name ne 'NULL');
            print Cfile "\t{0, $max, $name, $start}";
	}
	print Cfile "\n};\n";
    }

    close Cfile;
}

sub printAPIFile {
    my $file = shift;
    my $xmlDoc = shift;

    my @Hfile;
    @Hfile = openHeaderFiles($file);

    my $notice = $genNotice;
    $notice =~ s/\n/\n * /gs;
    $notice =~ s/\s+\n/\n/gs;

    foreach my $header (keys %headers) {
    	next unless $Hfile[$header];
	*Hfile = $Hfile[$header];
	my $include = "adt.h";
	my $adt_event_n = "_${pfx_ADT}_EVENT_H";
	if ($header > 0) {
	    $include = "${pfx_adt}_event.h";
	    $adt_event_n = "_${pfx_ADT}_EVENT_".$header."_H";
	}
	print Hfile <<EOF;
/*
 * $notice
 */

#ifndef $adt_event_n
#define	$adt_event_n

#include <bsm/$include>

#ifdef	__cplusplus
extern "C" {
#endif

/*
 * adt_put_event() status values.  Positive values are for kernel-generated
 * failure, -1 for user-space.  For ADT_SUCCESS, the adt_put_event() return_val
 * is not used; the convention is to set it to ADT_SUCCESS.
 */
#define	ADT_SUCCESS	0
#define	ADT_FAILURE	-1

EOF
    }

    foreach my $listName (sort keys %msg_list) {
	my $shortName = uc $listName;
	$shortName =~ s/_TEXT//;

        my ($listRef, $headref) = @{$msg_list{$listName}};
	my ($header, $start, $public, $deprecated) = @$headref;
	next unless $Hfile[$header];
	*Hfile = $Hfile[$header];

	print Hfile "/* Deprecated message list */\n" if $deprecated;
	print Hfile "#define\t${pfx_ADT}_$shortName\t$start\n" if $start;

	my @listValue =  @$listRef;
	next unless ($#listValue >= 0);
	print Hfile "enum\t${pfx_adt}_$listName", " {\n";

	my $listValue;
	my $i = 0;
	my $j = $#listValue;
	my $comma = ',';
	foreach $listValue (@listValue) {
	    my ($id, $text) = split(/\s*::\s*/, $listValue);
	    $comma = '' if $i++ == $j;
	    if ($start) {
		$start = " = $start$comma";
	    } else {
	        $start = "$comma\t";
	    }
	    $text = "(no token will be generated)" unless $text;
	    my $line = "\t${pfx_ADT}_$shortName"."_$id$start\t/* ";
	    # ensure whole line does not exceed 80 chars
	    my $eline = $line.$text;
	    #expand tabs
	    1 while $eline =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e;
	    if ((length($eline) > 77) && ($line =~ /\t\t/)) {
	    	# 77 = 80 - length(" */")
		# strip off double tab so that comment can be longer
		$line =~ s/\t\t/\t/;
		# shorten eline; don't mind where the spaces are removed, it is
		# only $eline length which matters
		$eline =~ s/ {8}//; 
	    }
	    if (length($eline) > 77) { # 80 - length(" */")
	    	# here we use negative length in substr to leave off from the
		# right side; 74 = 77 - length("...")
	    	$line .= substr($text, 0, 74 - length($eline));
		# strip off part of last word (already cut)
		$line =~ s/\s(\S+)$/ /;
		$line .= "...";
	    } else {
	    	$line .= $text;
	    }
	    print Hfile "$line */\n";
	    $start = '';
	}
	print Hfile "};\n";
    }

    # generate defines for external event names

    foreach my $eventId (sort keys %eventAPI) {
        my ($header, $idNo) = @{$eventExtra{$eventId}};
	unless (defined ($header)) {
	    print STDERR "missing header selection for $eventId\n";
	    next;
	}
	*Hfile = $Hfile[$header];
	next unless $Hfile[$header];

	my $l = length($eventId) + 8; # label plus preceding #define\t
	$l = 5 - int(($l + 8)/8);
	$l = 1 if $l < 1;
	my $tab = "\t" x $l;

        print STDERR "missing id number for $eventId\n" unless $idNo;

	$eventId =~ s/${pfx_AUE}_/${pfx_ADT}_/;
	print Hfile "#define\t$eventId$tab$idNo\n";
    }


    # generate per-event structures

    foreach my $eventId (sort keys %eventAPI) {
        my ($header, $idNo) = @{$eventExtra{$eventId}};
	my $dataId = $eventId;
	$dataId =~ s/^${pfx_AUE}_/${pfx_adt}_/;
	unless(defined ($header)) {
	    print STDERR "$eventId is missing the header assignment\n";
	    next;
	}
	*Hfile = $Hfile[$header];
	next unless $Hfile[$header];

	my $externalId = $eventId;
	$externalId =~ s/${pfx_AUE}_/${pfx_ADT}_/;

	print Hfile "\nstruct $dataId {\t/* $externalId */\n";

	my @entries = @{$eventAPI{$eventId}};
	my $entry;
	if ($#entries < 0) {
	    print Hfile "\tint\tdummy;\t/* not used */\n";
	} else {
	    foreach $entry (@entries) {
		$entry =~ s/termid/adt_termid_t/;
		print Hfile "\t$entry\n";
	    }
	}
	print Hfile "};\n";
	$eventId =~ s/^${pfx_AUE}_/${pfx_adt}_/;
	print Hfile "typedef struct $dataId $eventId","_t;\n";
    }

    foreach my $header (sort keys %headers) {
	$outputState[$header] = 0;
    }
    
    foreach my $eventId (sort keys %eventAPI) {
        my ($header, $idNo) = @{$eventExtra{$eventId}};
	unless(defined ($header)) {
	    # don't print duplicate error message
	    next;
	}
	*Hfile = $Hfile[$header];
	next unless $Hfile[$header];
	if ($outputState[$header] == 0) {
	    $outputState[$header] = 1;
	    my $suffix = '';
	    $suffix = "_$header" if $header;
	    print Hfile "\nunion adt_event_data$suffix {\n";
	}
        my $elementName = $eventId;
	$elementName =~ s/^${pfx_AUE}_/${pfx_adt}_/;
	$eventId =~ s/^${pfx_AUE}_/${pfx_adt}_/;
	$elementName =~ s/_t$//;
        
	print Hfile "\t\t$eventId","_t\t$elementName;\n";
    }
    foreach my $header (sort keys %headers) {
	if ($outputState[$header]) {
	    *Hfile = $Hfile[$header];
	    next unless $Hfile[$header];
	    print Hfile "};\n";
	}
    }
    foreach my $header (keys %headers) {
    	next unless $Hfile[$header];
	*Hfile = $Hfile[$header];
	my $adt_event_n = "_${pfx_ADT}_EVENT_H";
	if ($header > 0) {
	    $adt_event_n = "_${pfx_ADT}_EVENT_".$header."_H";
	}
	print Hfile <<EOF;


#ifndef	${pfx_ADT}_PRIVATE
#define	${pfx_ADT}_PRIVATE

/*
 * These interfaces are project private and will change without
 * notice as needed for the Solaris Audit project.
 */

extern	void	adt_get_auid(const adt_session_data_t *, au_id_t *);
extern	void	adt_set_auid(const adt_session_data_t *, const au_id_t);

extern	void	adt_get_mask(const adt_session_data_t *, au_mask_t *);
extern	void	adt_set_mask(const adt_session_data_t *, const au_mask_t *);

extern	void	adt_get_termid(const adt_session_data_t *, au_tid_addr_t *);
extern	void	adt_set_termid(const adt_session_data_t *,
    const au_tid_addr_t *);

extern	void	adt_get_asid(const adt_session_data_t *, au_asid_t *);
extern	void	adt_set_asid(const adt_session_data_t *, const au_asid_t);
extern	au_asid_t adt_get_unique_id(au_id_t);
extern	void	adt_load_table(const adt_session_data_t *, adt_translation_t **,
    void (*preload)(au_event_t, adt_event_data_t *));

extern	void	${pfx_adt}_preload(au_event_t, adt_event_data_t *);

extern adt_translation_t *${pfx_adt}_xlate_table[];

#endif

#ifdef	__cplusplus
}
#endif

#endif	/* $adt_event_n */
EOF
    }
    closeHeaderFiles(@Hfile);
}

sub generateTableC {
    my $event = shift;
    my $eventId = shift;
    my $eventType = shift;
    my $eventHeader = shift;
    my $omit = shift;

    my %tokenType = (
	#
	#	tokenTypes are the ones that are actually defined
	#	for use in adt.xml audit records
	#

	#	  'acl'			=> 'AUT_ACL',		# not defined
	#	  'arbitrary'		=> 'AUT_ARBITRARY',	# not defined
	#	  'arg'			=> 'AUT_ARG',		# not defined
	#	  'attr'		=> 'AUT_ATTR',
		  'command'		=> 'AUT_CMD',
		  'command_alt'		=> 'ADT_CMD_ALT',	# dummy token id
	#	  'date'		=> 'AUT_TEXT',		# not used
	#	  'exec_args'   	=> 'AUT_EXEC_ARGS',	# not defined
	#	  'exec_env'    	=> 'AUT_EXEC_ENV',	# not defined
	#	  'exit'        	=> 'AUT_EXIT',		# not defined
		  'fmri'        	=> 'AUT_FMRI',
	#	  'groups'      	=> 'AUT_GROUPS',	# not defined
	#	  'header'      	=> 'AUT_HEADER',	# not defined
		  'in_peer'     	=> 'ADT_IN_PEER',	# dummy token id
		  'in_remote'     	=> 'ADT_IN_REMOTE',	# dummy token id
	#	  'ipc'         	=> 'AUT_IPC',		# not defined
	#	  'ipc_perm'    	=> 'AUT_IPC_PERM',	# not defined
		  'iport'		=> 'AUT_IPORT',
		  'label'		=> 'AUT_LABEL',
		  'newgroups'   	=> 'AUT_NEWGROUPS',
	#	  'opaque'      	=> 'AUT_OPAQUE',	# not defined
		  'path'        	=> 'AUT_PATH',
		  'path_list'		=> '-AUT_PATH',		# dummy token id
		  'process'     	=> 'AUT_PROCESS',
		  'priv_effective'	=> 'ADT_AUT_PRIV_E',	# dummy token id
		  'priv_limit'		=> 'ADT_AUT_PRIV_L', 	# dummy token id
		  'priv_inherit'	=> 'ADT_AUT_PRIV_I',	# dummy token id
		  'return'      	=> 'AUT_RETURN',
		  'secflags'		=> 'AUT_SECFLAGS',
	#	  'seq'         	=> 'AUT_SEQ',		# not defined
	#	  'socket'      	=> 'AUT_SOCKET',	# not defined
	#	  'socket-inet' 	=> 'AUT_SOCKET_INET',
		  'subject'     	=> 'AUT_SUBJECT',
		  'text'        	=> 'AUT_TEXT',
		  'tid'          	=> 'AUT_TID',
	#	  'trailer'     	=> 'AUT_TRAILER',	# not defined
		  'uauth'		=> 'AUT_UAUTH',
		  'user'		=> 'AUT_USER',
		  'zonename'		=> 'AUT_ZONENAME'
		 );

    my @xlateEntryList = ();

    my $external = $event->getExternal();
    my $internal = $event->getInternal();

    unless ($external) {
	print STDERR "No external object captured for event $eventId\n";
	return;
    }
    if ($eventType) {
	$nameTranslation{$eventId} = $eventId;
    } else {
	$nameTranslation{$eventId} = $external->getInternalName();
    }
    unless ($internal) {
	print STDERR "No internal object captured for event $eventId\n";
	return;
    }
    my @entryRef = $internal->getEntries();
    my $entryRef;
    my @tokenOrder = ();
    my $firstTokenIndex = 0; # djdj not used yet, djdj BUG!
    			     # needs to be used by translate table

    if ($internal->isReorder()) { # prescan the entry list to get the token order
      my @inputOrder;
      foreach $entryRef (@entryRef) {
	my ($intEntry, $entry) = @$entryRef;
	push (@inputOrder, $intEntry->getAttr('order'));
      }

      my $i; # walk down the inputOrder list once
      my $k = 1; # discover next in line
      my $l = 0; # who should point to next in line
      for ($i = 0; $i <= $#inputOrder; $i++) {
	my $j;
	for ($j = 0; $j <= $#inputOrder; $j++) {
	  if ($k == $inputOrder[$j]) {
	    if ($k == 1) {
	        $firstTokenIndex = $j;
	    } else {
	        $tokenOrder[$l] = "&(selfReference[$j])";
	    }
	    $l = $j;
	    last;
	  }
	}
	$k++;
      }
      $tokenOrder[$l] = 'NULL';
    }
    else { # default order -- input order same as output
      my $i;
      my $j;
      for ($i = 0; $i < $#entryRef; $i++) {
	my $j = $i + 1;
	$tokenOrder[$i] = "&(selfReference[$j])";
      }
      $tokenOrder[$#entryRef] = 'NULL';
    }

    my $sequence = 0;
    foreach $entryRef (@entryRef) {
      my ($intEntry, $entry) = @$entryRef;
      my $entryId = $entry->getAttr('id');

      my ($extEntry, $unusedEntry, $tokenId) =
	$external->getEntry($entryId);
      my $opt = $extEntry->getAttr('opt');

      if ($opt eq 'none') {
	if (defined ($doc->getToken($tokenId))) {
	  if (defined ($tokenType{$tokenId})) {
	    $tokenId = $tokenType{$tokenId};
	  }
	  else {
	    print STDERR "token id $tokenId not implemented\n";
	  }
	}
	else {
	  print STDERR "token = $tokenId is undefined\n";
	  $tokenId = 'error';
	}
	my ($xlate, $jni) =
	  formatTableEntry ('', $tokenId, $eventId, '', 0, 0,
			    $tokenOrder[$sequence], 'NULL', '', $omit);
	push (@xlateEntryList, $xlate);
      }
      else {
	my $dataType = $extEntry->getAttr('type');
	$dataType =~ s/\s+//g;   # remove blanks (char * => char*)

	my $enumGroup = '';
	if ($dataType =~ /^msg/i) {
	    $enumGroup = $dataType;
	    $enumGroup =~ s/^msg\s*//i;
	    $enumGroup = "${pfx_adt}_" . $enumGroup;
	}
	my $required = ($opt eq 'required') ? 1 : 0;
	my $tsol = 0;
	my $tokenId = $intEntry->getAttr('token');
	my $token;
	my $tokenName;
	my $tokenFormat = $intEntry->getAttr('format');
	if (defined ($tokenFormat)) {
	  $tokenFormat = "\"$tokenFormat\"";
	}
	else {
	  $tokenFormat = 'NULL';
	}
	
	if (defined ($token = $doc->getToken($tokenId))) {
	  $tsol = (lc $token->getUsage() eq 'tsol') ? 1 : 0;
	  if (defined ($tokenType{$tokenId})) {
	    $tokenName = $tokenType{$tokenId};
	  }
	  else {
	    print STDERR "token id $tokenId not implemented\n";
	  }
	}
	else {
	  print STDERR 
	    "$tokenId is an unimplemented token ($entryId in $eventId)\n";
	  $tokenName = 'AUT_TEXT';
	}
	my ($xlate, $jni) =
	  formatTableEntry($entryId, $tokenName, $eventId, $dataType, $required,
			   $tsol, $tokenOrder[$sequence], $tokenFormat,
			   $enumGroup, $omit);
	push (@xlateEntryList, $xlate);
      }
      $sequence++;
    }
    $xlateEventTable{$eventId} = [\@xlateEntryList, $eventType, $firstTokenIndex,
				 $eventHeader];
}

sub formatTableEntry {
    my ($id, $token, $eventId, $type, $required, $tsol, $sequence, $format,
	$enumGroup, $omitEntry) = @_;


    # does this map belong in the xml source?  (at least the defaults?)
    # fill in the default value only if it is other than zero.
    #		      base type		    adt name,	default value
    my %entryDef = ( 'au_asid_t'       	=> ['ADT_UINT32',	''],
		     'uint_t'		=> ['ADT_UINT32',      	''],
		     'int'		=> ['ADT_INT',		''],
		     'int32_t'		=> ['ADT_INT32',	''],
		     'uid_t'		=> ['ADT_UID',		'AU_NOAUDITID'],
		     'gid_t'		=> ['ADT_GID',		'AU_NOAUDITID'],
		     'uid_t*'		=> ['ADT_UIDSTAR',	''],
		     'gid_t*'		=> ['ADT_GIDSTAR',	''],
		     'char'		=> ['ADT_CHAR',		''],
		     'char*'		=> ['ADT_CHARSTAR',	''],
		     'char**'		=> ['ADT_CHAR2STAR',	''],
		     'long'		=> ['ADT_LONG',		''],
		     'pid_t'		=> ['ADT_PID',		''],
		     'priv_set_t*'	=> ['ADT_PRIVSTAR',	''],
		     'ulong_t'		=> ['ADT_ULONG',	''],
		     'uint16_t',	=> ['ADT_UINT16',	''],
		     'uint32_t'		=> ['ADT_UINT32',	''],
		     'uint32_t*'	=> ['ADT_UINT32STAR',	''],
		     'uint32_t[]'	=> ['ADT_UINT32ARRAY',  ''],
		     'uint64_t'		=> ['ADT_UINT64',	''],
		     'uint64_t*'	=> ['ADT_UINT64STAR',	''],
		     'm_label_t*'	=> ['ADT_MLABELSTAR',	''],
		     'fd_t'		=> ['ADT_FD',		'-1'],
		    );
    my $xlateLabel = $uniLabel.$xlateUniLabelInc;
    my $xlateLabelInc = 0;
    my $xlateLine = '';
    my @jniLine = ();

	# the list handling should be a simple loop with a loop of one
        # falling out naturally.

    unless ($type =~ /,/) {	# if list, then generate sequence of entries
      my $dataType;
      my $dataSize;
      my $xlateLabelRef = '';

      my $arraySize = '';
      $arraySize = $1 if ($type =~ s/\[(\d+)\]/[]/);

      my $entryType = ${$entryDef{$type}}[0];

      my @xlateType = ();	# for adt_xlate.c
      my $typeCount = 1;

      if ($entryType) {
	$dataType = $entryType;
	$type =~ s/([^*]+)\s*(\*+)/$1 $2/;
	$type =~ s/\[\]//;
	$dataSize = "sizeof ($type)";
	if ($arraySize) {
		$dataSize = "$arraySize * " . $dataSize;
	}
	$xlateLine = "{{$dataType, $dataSize}}";
	push (@jniLine, [$id, $dataType, $format, $enumGroup, $required]);
      } elsif ($type eq '') {
	  $xlateLabelRef = 'NULL';
      } elsif ($type =~ /^msg/i) {
	$type =~ s/^msg//i;
	$dataType = 'ADT_MSG';
	my $dataEnum = 'ADT_LIST_' . uc $type;
	$xlateLine = "{{$dataType, $dataEnum}}";
	push (@jniLine, [$id, $dataType, $format, $enumGroup, $required]);
      } elsif ($type =~ /time_t/i) {
	$dataType = 'ADT_DATE';
	$dataSize = "sizeof (time_t)";
	$xlateLine = "{{$dataType, $dataSize}}";
	push (@jniLine, [$id, $dataType, $format, $enumGroup, $required]);
      } elsif ($type =~ /termid/i) {
	$dataType = 'ADT_TERMIDSTAR';
	$dataSize = "sizeof (au_tid_addr_t *)";
	$xlateLine = "{{$dataType, $dataSize}}";
	push (@jniLine, [$id, $dataType, $format, $enumGroup, $required]);
      } elsif (uc $omitEntry eq 'JNI') {
	$xlateLabelRef = 'NULL';
      } else {
	print STDERR "$type is not an implemented data type\n";
	$xlateLabelRef = 'NULL';
      }
      if ($xlateLine && !($xlateTypeList{$xlateLine})) {
	$xlateTypeList{$xlateLine} = $xlateLabel;
	push (@xlateTypeList, "datadef\t$xlateLabel\[1\] =\t$xlateLine;");
	$xlateLabelInc = 1;
      } else {
	$xlateLabel = $xlateTypeList{$xlateLine};
      }
      $xlateLabelRef = '&' . $xlateLabel . '[0]'
	unless $xlateLabelRef eq 'NULL';

      # "EOL" is where a comma should go unless end of list
      $xlateLine = "{$token,\t1,\t$xlateLabelRef,\t$sequence,\n" .
	  "\t\t0,\t$required,\t$tsol,\t$format}EOL";
      
      if (uc $omitEntry ne 'ALWAYS' && ${$entryDef{$type}}[1]) {
	  my @list = ();
	  if ($xlateDefault{$eventId}) {
	      @list = @{$xlateDefault{$eventId}};
	  } else {
	      push (@xlateDefaults, $eventId);
	  }
	  push (@list, $id, ${$entryDef{$type}}[1]);
	  $xlateDefault{$eventId} = \@list;
      }
    } else {	# is a list
      my @type = split(/,/, $type);
      my @arraySize = ();
      my @id   = split(/,/, $id);
      my @jniId  = @id;
      my $dataType;
      my $typeCount = ($#type + 1);
      my @xlateType = ();
      my @default = ();

      foreach my $dtype (@type) {
	my $jniId = shift @jniId;
	my $id = shift @id;
	my $arraySize = '';
	$arraySize = $1 if ($dtype =~ s/\[(\d+)\]/[]/);

	my $entryType = ${$entryDef{$dtype}}[0];
	if ($entryType) {
	  my $type = $dtype;
	  $type =~ s/([^*]+)\s*(\*+)/$1 $2/;
	  $type =~ s/\[\]//;

	  my $sizeString = "sizeof";
	  $sizeString = "$arraySize * " . $sizeString if $arraySize;
	  push (@xlateType, "\{$entryType, $sizeString ($type)\}");
	  push (@jniLine, [$jniId, $entryType, $format, $enumGroup, $required]);
	} elsif ($type =~ /^msg/i) {
	  $type =~ s/^msg//i;
	  $dataType = 'ADT_MSG';
	  my $dataEnum = 'ADT_LIST_' . uc $type;
	  push (@xlateType, "\{$dataType, $dataEnum\}};");
	  push (@jniLine, [$jniId, $dataType, $format, $enumGroup, $required]);
	} elsif ($type =~ /time_t/i) {
	  $dataType = 'ADT_DATE';
	  push (@xlateType, "\{$entryType, sizeof ($type)\}");
	  push (@jniLine, [$jniId, $entryType, $format, $enumGroup, $required]);
	} elsif ($type =~ /termid/i) {
	  $dataType = 'ADT_TERMIDSTAR';
	  push (@xlateType, "\{$dataType, sizeof (au_tid_addr_t *)\}");
	  push (@jniLine, [$jniId, $dataType, $format, $enumGroup, $required]);
	} elsif (uc $omitEntry eq 'JNI') {
	  # nothing to do.
	} else {
	  print STDERR "$dtype is not an implemented data type\n";
	}
	if (uc $omitEntry ne 'ALWAYS' && ${$entryDef{$dtype}}[1]) {
	  push (@default, $id, ${$entryDef{$dtype}}[1]);
	}
      }
      my $xlateArray = "\[$typeCount\] =\t{" . join(",\n\t\t\t\t", @xlateType) . "};";
      
      unless ($xlateTypeList{$xlateArray}) {
	$xlateTypeList{$xlateArray} = $xlateLabel;
	$xlateArray = "datadef\t$xlateLabel" . $xlateArray;
	push (@xlateTypeList, $xlateArray);
	$xlateLabelInc = 1;
      } else {
	$xlateLabel = $xlateTypeList{$xlateArray};
      }
      $xlateLine =
	"{$token,\t$typeCount,\t&$xlateLabel\[0\],\t$sequence,\n" .
        "\t\t0,\t$required,\t$tsol,\t$format}EOL";
      if (@default) {
	  my @list = ();
	  if ($xlateDefault{$eventId}) {
	      @list = @{$xlateDefault{$eventId}};
	  } else {
	      push (@xlateDefaults, $eventId);
	  }
	  push (@list, @default);
	  $xlateDefault{$eventId} = \@list;
      }
    }
    $xlateUniLabelInc++ if $xlateLabelInc;
    return ($xlateLine, \@jniLine);
}

sub generateAPIFile {
    my $event = shift;
    my $eventId = shift;
    my $eventType = shift;
    my $eventHeader = shift;
    my $idNo = shift;

    my @entryList = ();

    my $external = $event->getExternal();

    if ($eventType && $debug) {
	print STDERR "event $eventId is of type $eventType\n";
    }

    return unless $external;

    my ($extEntry, $entry, $tokenId, $format);
    while (($extEntry, $entry, $tokenId, $format) = $external->getNextEntry()) {
	last unless $entry;
	my $entryId = $entry->getAttr('id');

	unless (defined $entryId) {
	    print STDERR "undefined entry id for external $eventId\n";
	    next;
	}
	my $option = $extEntry->getAttr('opt');
	next if ($option eq 'none');

	if (defined (my $token = $doc->getToken($tokenId))) {
	  $option = 'Trusted Solaris only'
	    if (lc $token->getUsage() eq 'tsol') ? 1 : 0;
	}
	$option .= " (format: $format)" if $format;

	my $dataType = $extEntry->getAttr('type');
	unless (defined $dataType) {
	  print STDERR "no type defined for external tag for $eventId\n";
	  $dataType = "error";
	}

	my $comment = $entry->getContent();

	if (($dataType =~ /,/) || ($entryId =~ /,/)) {
	  my @type = split(/\s*,\s*/, $dataType);
	  my @id   = split(/\s*,\s*/, $entryId);
	  if ($#type != $#id) {
	    print STDERR
	      "number of data types ($dataType) does not match number of ids ($entryId)",
	      " for event $eventId\n";
	    if ($#type < $#id) {
	      $#id = $#type;
	    }
	    else {
	      $#type = $#id;
	    }
	  }

	  my $i;
	  my $line = '';
	  $line = "/* $comment */\n\t" if defined $comment;
	  for ($i = 0; $i <= $#type; $i++) {
	    my ($primitive, $dereference) =
	        ($type[$i] =~ /([^\*]+)\s*(\**)/);
	    $id[$i] .= $1 if ($primitive =~ s/(\[\d+\])//);
	    $line .= "$primitive\t$dereference$id[$i];\t/*  $option  */";
	    push (@entryList, $line);
	    $line = '';
	  }
	}
	else {
	  my $line = '';
	  $line = "/* $comment */\n\t" if defined $comment;
	  if ($dataType =~ /^msg/i) {
	      $dataType =~ s/^msg\s*//i;
	      $line .= "enum ${pfx_adt}_$dataType" . "\t$entryId;\t/*  $option  */";
	  }
	  elsif ($dataType =~ /time_t/i) {
	      $line .= "time_t\t$entryId;\t/* $option */";
	  }
	  else {
	    my ($primitive, $dereference) =
	        ($dataType =~ /([^\*]+)\s*(\**)/);
	    $entryId .= $1 if ($primitive =~ s/(\[\d+\])//);
	    $line .= "$primitive\t$dereference$entryId;\t/* $option */";
	  }
	  push (@entryList, $line);
	}
    }
    $eventExtra{$eventId} = [$eventHeader, $idNo];
    $eventAPI{$eventId} = \@entryList;
}

sub generateMsgLists {
    my $textList = shift;

    my $textName = $textList->getId();
    my $header = $textList->getHeader();
    my $start = $textList->getMsgStart();
    my $public = $textList->getMsgPublic();
    my $deprecated = $textList->getDeprecated();

    addHeader($header);
    print "$textName starts at $start\n" if $debug;

    my $entry;
    my @entry;
    while ($entry = $textList->getNextMsg()) {
        if ($debug) {
	    my ($id, $text) = split(/\s*::\s*/, $entry);
	    print "   $id = $text\n";
	}
	unshift (@entry, $entry);
    }
    $msg_list{$textName} =
	[\@entry, [$header, $start, $public, $deprecated]];
}

sub addHeader {
    my $header_index = shift;

    die "invalid adt_event_N.h index: $header_index\n"
        unless ($header_index =~ /^\d+$/);

    $headers{$header_index} = $header_index;
}

# $header = 0 is a special case; it is for adt_event.h
# $header > 0 creates adt_event_N.h, where N = $header

sub openHeaderFiles {
    my $outfile = shift;	# path to an adt_event_N.h file

    my $header;
    my @Hfile = (); # potentially sparse array of file handles
    my @HfileName = (); # parallel array to Hfile, file name (not path)
    foreach $header (sort keys %headers) {
        my $file = $outfile;
	if ($header > 0) {
	    $file =~ s/_N/_$header/;
	} else {
	    $file =~ s/_N//;
	}
	unless (open($Hfile[$header], ">$file")) {
	    print STDERR "can't open output ($file): $!\n";
	    $HfileName[$header] = '';
	    $Hfile[$header] = '';
	} else {
	    my @tmp = split(/\//, $file);
	    $HfileName[$header] = $tmp[$#tmp];
	}
    }
    return (@Hfile);
}

sub closeHeaderFiles {
    my @Hfile = @_;

    my $header;
    foreach $header (sort keys %headers) {
	close $Hfile[$header] if $Hfile[$header];
    }
}
